package zdream.control.region;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

import zdream.utils.math.Rectangle;

/**
 * <p>在区域中标定的怪物生成的点位, 属于区域
 * <p>
 * 怪物参数的来源有很多, 主要分成三部分, 每个部分可以覆盖前一个部分中相同的参数
 * (怪物内部的 class 设置的, 我们就不管了)
 * </p>
 * <ul>
 * <li>原始参数应该是从 foe 的 json 中获取, 比如 "foes/mm2birds/mm2bird.json"
 * <li>每个关卡有对 foe 描述的修改, 比如 "level/level1/cut.json"
 * <li>在最后生成的 foe 的 {@link zdream.control.world.LevelWorld} 中传入的 param
 * </ul>
 * 这里的这个 param 是第二部分.
 *
 * @author Zdream
 * @since 0.0.1
 * @date
 *   2019-06-02 (created)
 *   2019-06-02 (last modified)
 */
public class ItemSpec implements Cloneable, Serializable {
	/**
	 * 哪类敌人, 怪物 / 场的名字
	 */
	public String name;

	private ItemSpec() {
		this(null);
	}

	public ItemSpec(String name) {
		this.name = name;
	}

	@Override
	public ItemSpec clone() {
		ItemSpec spec;

		try {
			spec = (ItemSpec) super.clone();
		} catch (CloneNotSupportedException e) {
			throw new RuntimeException(e);
		}

		spec.field = field == null ? null : spec.field.clone();
		return spec;
	}

	/* ************
	 * Parameter  *
	 ************ */
	/*
	 * 这里放了常用的 foe 的初始化参数
	 */

	public boolean xSet, ySet;
	public float x, y;
	public Boolean orientation;

	public ItemSpec withPosition(float x, float y) {
		return this.withX(x).withY(y);
	}
	public ItemSpec withX(float x) {
		this.x = x;
		this.xSet = true;
		return this;
	}
	public ItemSpec withY(float y) {
		this.y = y;
		this.ySet = true;
		return this;
	}
	public ItemSpec withOrientation(boolean orientation) {
		this.orientation = orientation;
		return this;
	}
	public ItemSpec withOrientation(Boolean orientation, boolean defaults) {
		boolean value = orientation == null ? defaults : orientation;
		return withOrientation(value);
	}

	public String type;
	public Byte side;
	public ItemSpec withType(String type) {
		this.type = type;
		return this;
	}
	public ItemSpec withSide(String sideText) {
		this.side = parseSide(sideText);
		return this;
	}

	public static class Field implements Cloneable {
		public final ItemSpec parent;
		public boolean boxTop;
		public float width, height;
		public float spawnX, spawnY;
		public boolean spawnXSet, spawnYSet;
		public byte side;
		public Field(ItemSpec parent) {
			this.parent = parent;
		}
		public Field withBoxTop(boolean boxTop) {
			this.boxTop = boxTop;
			return this;
		}
		public Field withSpawnX(float fieldSpawnX) {
			this.spawnX = fieldSpawnX;
			this.spawnXSet = true;
			return this;
		}
		public Field withSpawnY(float fieldSpawnY) {
			this.spawnY = fieldSpawnY;
			this.spawnYSet = true;
			return this;
		}
		public Field withSide(String sideText) {
			this.side = parseSide(sideText);
			return this;
		}
		public ItemSpec create() {
			return parent;
		}
		@Override
		public Field clone() {
			try {
				return (Field) super.clone();
			} catch (CloneNotSupportedException e) {
				throw new RuntimeException(e);
			}
		}
	}
	public Field field;
	public Field withField() {
		return field = new Field(this);
	}
	public ItemSpec withFieldRectangle(Rectangle rectangle) {
		withPosition(rectangle.x, rectangle.y);
		Field field = this.field == null ? withField() : this.field;
		field.width = rectangle.width;
		field.height = rectangle.height;
		return this;
	}

	public static class Pattern implements Cloneable {
		public String name;
		public float x, y, width, height;
		public boolean base;
		public Pattern(String name, float x, float y, float width, float height, boolean base) {
			this.name = name;
			this.x = x;
			this.y = y;
			this.width = width;
			this.height = height;
			this.base = base;
		}
	}
	public List<Pattern> patterns = null;
	public ItemSpec withPattern(String name, float x, float y, float width, float height, boolean base) {
		(patterns == null ? (patterns = new ArrayList<>()) : patterns)
				.add(new Pattern(name, x, y, width, height, base));
		return this;
	}

	public static interface ModuleSpec {
	}
	public List<ModuleSpec> modules = null;

	/* ************
	 *   Parser   *
	 ************ */

	private static byte parseSide(String sideText) {
		switch (sideText) {
		case "player": return 1;
		case "enemy": return 2;
		case "neutral": return 3;
		case "other": return 4;
		default: return 0;
		}
	}

	/* ************
	 *  Builder   *
	 ************ */

	/**
	 * <blockquote><pre>
	 * {
	 *   "name" : "MM2ShotMan",
	 *   "box" :
	 *   {
	 *     "orientation" : false
	 *   }
	 * }
	 * </pre></blockquote>
	 */
	public static ItemSpec ofJson(JsonObject param) {
		if (param == null) {
			return new ItemSpec();
		}

		ItemSpec spec = new ItemSpec(getString(param, "name"));
		String text;

		if (param.has("box")) {
			JsonObject oBox = param.getAsJsonObject("box");
			getBool(oBox, "orientation", spec::withOrientation);
			getFloat(oBox, "x", spec::withX);
			getFloat(oBox, "y", spec::withY);
		}

		spec.type = getString(param, "type");
		getString(param, "side", spec::withSide);

		if (param.has("field")) {
			JsonObject oField = param.getAsJsonObject("field");
			final Field field = spec.withField();
			if ((text = oField.get("top").getAsString()) != null) {
				field.withBoxTop(Objects.equals(text, "no_border"));
			}
			getString(oField, "side", field::withSide);
			getFloat(oField, "x", field::withSpawnX);
			getFloat(oField, "y", field::withSpawnY);
		}

		// pattern
		if (param.has("patterns")) {
			JsonArray array = param.getAsJsonArray("patterns");
			for (JsonElement element : array) {
				final JsonObject o = element.getAsJsonObject();
				boolean base = o.has("base") ? o.get("base").getAsBoolean() : false;
				spec.withPattern(
						o.get("name").getAsString(),
						o.get("x").getAsFloat(),
						o.get("y").getAsFloat(),
						o.get("width").getAsFloat(),
						o.get("height").getAsFloat(),
						base
				);
			}
		}

		// modules TODO

		return spec;
	}

	public static ItemSpec ofJson(String param) {
		if (param == null) {
			return new ItemSpec();
		}
		return ofJson(JsonParser.parseString(param).getAsJsonObject());
	}

	private static String getString(JsonObject object, String key) {
		final JsonElement element = object.get(key);
		return element == null ? null : element.getAsString();
	}

	private static void getString(JsonObject object, String key, Consumer<String> setIfPresent) {
		final JsonElement element = object.get(key);
		if (element != null) {
			setIfPresent.accept(element.getAsString());
		}
	}

	private static void getBool(JsonObject object, String key, Consumer<Boolean> setIfPresent) {
		final JsonElement element = object.get(key);
		if (element != null) {
			setIfPresent.accept(element.getAsBoolean());
		}
	}

	private static void getFloat(JsonObject object, String key, Consumer<Float> setIfPresent) {
		final JsonElement element = object.get(key);
		if (element != null) {
			setIfPresent.accept(element.getAsFloat());
		}
	}

	private static void getByte(JsonObject object, String key, Consumer<Byte> setIfPresent) {
		final JsonElement element = object.get(key);
		if (element != null) {
			setIfPresent.accept(element.getAsByte());
		}
	}

	public static ItemSpec empty() {
		return new ItemSpec();
	}

	public static ItemSpec of(String name, float x, float y, boolean orientation) {
		return new ItemSpec(name).withPosition(x, y).withOrientation(orientation);
	}
}
